#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <signal.h>
#include "mongoose.h"
#include "ptylib.h"

static const char *s_url = "mqtt://47.100.111.210:1883";
static struct mg_connection *s_conn; // Client connection
static struct mg_connection *tty_conn; // Client connection
static const char *s_pub_topic_fmt = "mg/%s/toboard"; // Publish topic
static const char *s_sub_topic_fmt = "mg/%s/toclient"; // Subscribe topic
static char s_pub_topic[64]; // Publish topic
static char s_sub_topic[64]; // Subscribe topic
static int s_qos = 0; // MQTT QoS

char buf[4096];
static void fn(struct mg_connection *c, int ev, void *ev_data)
{
	if (ev == MG_EV_OPEN) {
		MG_INFO(("%lu CREATED", c->id));
		// c->is_hexdumping = 1;
	} else if (ev == MG_EV_ERROR) {
		// On error, log error message
		MG_ERROR(("%lu ERROR %s", c->id, (char *)ev_data));
	} else if (ev == MG_EV_MQTT_OPEN) {
		// MQTT connect is successful
		struct mg_str subt = mg_str(s_sub_topic);
		MG_INFO(("%lu CONNECTED to %s", c->id, s_url));
		struct mg_mqtt_opts sub_opts;
		memset(&sub_opts, 0, sizeof(sub_opts));
		sub_opts.topic = subt;
		sub_opts.qos = s_qos;
		mg_mqtt_sub(c, &sub_opts);
		MG_INFO(("%lu SUBSCRIBED to %.*s", c->id, (int)subt.len, subt.buf));
	} else if (ev == MG_EV_MQTT_MSG) {
		// When we get echo response, print it
		struct mg_mqtt_message *mm = (struct mg_mqtt_message *)ev_data;
		int term = *(int *)c->fn_data;
		//MG_INFO(("%lu RECEIVED %.*s <- %.*s", c->id, (int)mm->data.len, mm->data.buf, (int)mm->topic.len, mm->topic.buf));
		write(term, mm->data.buf, mm->data.len);
	}
}

static void tty_cb(struct mg_connection *c, int ev, void *ev_data)
{
	if (ev == MG_EV_READ) {
		struct mg_iobuf *r = &c->recv;
		struct mg_mqtt_opts pub_opts;
		struct mg_str pubt = mg_str(s_pub_topic), data = mg_str_n((void *)r->buf, r->len);
		memset(&pub_opts, 0, sizeof(pub_opts));
		pub_opts.topic = pubt;
		pub_opts.message = data;
		pub_opts.qos = s_qos, pub_opts.retain = false;
		mg_mqtt_pub(s_conn, &pub_opts);
		r->len = 0;
		//MG_INFO(("%lu PUBLISHED %.*s -> %.*s", s_conn->id, (int)data.len, data.buf, (int)pubt.len, pubt.buf));
	}
}

int main(int argc, char **argv)
{
	if (argc < 2) {
		printf("usage:client sn\n");
		exit(0);
	}
	snprintf(s_pub_topic, sizeof(s_pub_topic), s_pub_topic_fmt, argv[1]);
	snprintf(s_sub_topic, sizeof(s_sub_topic), s_sub_topic_fmt, argv[1]);

	int fds[2];
	if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds) < 0) {
		perror("socketpair");
		exit(0);
	}

	if (fork() == 0) {
		close(fds[1]);
		fd_set rset, org_rset;
		FD_ZERO(&org_rset);
		FD_SET(STDIN_FILENO, &org_rset);
		FD_SET(fds[0], &org_rset);
		int maxfd = fds[0] > STDIN_FILENO ? fds[0] : STDIN_FILENO;
		for (;;) {
			rset = org_rset;
			if (select(maxfd + 1, &rset, NULL, NULL, NULL) <= 0) {
				continue;
			}
			if (FD_ISSET(STDIN_FILENO, &rset)) {
				int len = read(STDIN_FILENO, buf, sizeof(buf));

				if (len <= 0) {
					perror("read STDIN_FILENO\n");
					kill(getppid(), SIGTERM);
					exit(0);
				}
				write(fds[0], buf, len);
			}
			if (FD_ISSET(fds[0], &rset)) {
				int len = read(fds[0], buf, sizeof(buf));
				if (len <= 0) {
					perror("read board\n");
					kill(getppid(), SIGTERM);
					exit(0);
				}
				if (strncmp(buf, "bye", 3) == 0) {
					write(STDOUT_FILENO, buf, len);
					kill(getppid(), SIGTERM);
					exit(0);
				}
				write(STDOUT_FILENO, buf, len);
			}
		}
	}

	tty_raw(STDIN_FILENO);
	close(fds[0]);
	struct mg_mgr mgr;
	//mg_log_set(MG_LL_INFO);
	mg_mgr_init(&mgr);
	struct mg_mqtt_opts opts = { //
				     .clean = true, //
				     .qos = s_qos, //
				     .topic = mg_str(s_pub_topic), //
				     .version = 4, //
				     .user = mg_str("admin"), //
				     .pass = mg_str("JZkj@8888"),
				     .message = mg_str("bye")
	};

	s_conn = mg_mqtt_connect(&mgr, s_url, &opts, fn, &fds[1]);
	tty_conn = mg_wrapfd(&mgr, fds[1], tty_cb, NULL);

	while (1)
		mg_mgr_poll(&mgr, 1000); // Event loop, 1s timeout
	mg_mgr_free(&mgr); // Finished, cleanup
}